﻿using System;
using System.Collections;
using System.Collections.Generic;
using System.Configuration;
using System.IO;
using System.Linq;
using System.Net;
using System.Net.Sockets;
using System.Text;
using System.Threading;
using System.Threading.Tasks;
using PConexion;
using PProtocolo;
using System.IO;
using System.Runtime.InteropServices;
using System.Security.Cryptography;
using Eventos;

namespace Servidor
{
    public class Server
    {
        //comentario
        //comentario santiago
        //ComentarioGerman
        private static int clientNumber = 1;
        private static string carpetaDeTrabajo;
        private static string ipServidor;
        private static int puertoServidor;

        static void Main(string[] args)
        {
            carpetaDeTrabajo = ConfigurationManager.AppSettings["CARPETA_DE_TRABAJO"];
            ipServidor = ConfigurationManager.AppSettings["IP_SERVIDOR"];
            puertoServidor = Int32.Parse(ConfigurationManager.AppSettings["PUERTO"]);
            Console.Title = "SERVIDOR - IP: "+ipServidor;
   
            ConexionServidor conexion=new ConexionServidor();
            if (conexion.AbrirConexion(ipServidor, puertoServidor))
            {
                Console.WriteLine("SERVIDOR: Se inició el servidor. Aceptando conexiones...\n");
                while (true)
                {
                    ConexionServidor conexion_cliente = conexion.ObtenerConexionEntrante();
                    Thread clienteThread = new Thread(() => ManejarSolicitud(conexion_cliente));
                    clienteThread.Start();
                }
            }
            else
            {
                Console.WriteLine("SERVIDOR: No se pudo abrir la conexion.\n");
            }
            Console.ReadLine();            
        }

        static void ManejarSolicitud(ConexionServidor clienteConexion)
        {
            string idSesion = ObtenerIdSesionGenerico();
            bool connected = true;
            Console.WriteLine("SERVIDOR: Cliente conectado número " + idSesion + "\n");

            IPEndPoint remoteIPEndpoint = clienteConexion.SocketConexion.RemoteEndPoint as IPEndPoint;
            String IPCliente = remoteIPEndpoint.Address.ToString();

            ProtocoloServidor protocoloServidor = new ProtocoloServidor();
            protocoloServidor.EstablecerConexion(clienteConexion);
            try
            {

                clienteConexion.RegistrarConexionServidor(idSesion, IPCliente, "Se conecto el cliente.");
            
            }
            catch (Exception ex)
            {
                Console.WriteLine("Servidor: No pudo registrarse la conexion en el servidor de conciliacion.");
            }

            EnviarIdSesion(idSesion, protocoloServidor);
            
            char delimitador = '|';

            while (connected)
            {
                try
                {
                    Trama tramaRecibida;
                    tramaRecibida = protocoloServidor.RecibirTrama();

                    if (clienteConexion.SocketConexion.Connected)
                    {
                        switch (tramaRecibida.Comando)
                        {
                            case 50:
                                //Enviamos la cantidad de archivos a listar

                                ArrayList archivos = listarArchivos();

                                Trama trama = new Trama();
                                trama.Comando = 50;
                                trama.Header = Protocolo.Tipo.RES;

                                int largo = archivos.Count.ToString().Length;//Cuento la cantidad de digitos del total de archivos para definir el largo de la trama en datos.

                                trama.Largo = largo;

                                trama.Datos = archivos.Count.ToString();

                                protocoloServidor.EnviarTrama(trama);

                                foreach (var archivo in archivos)
                                {
                                    Trama tramaArchivo = new Trama();
                                    tramaArchivo.Header = Protocolo.Tipo.RES;
                                    tramaArchivo.Comando = 51;

                                    int largoDatos = archivo.ToString().Length;

                                    tramaArchivo.Largo = largoDatos;

                                    tramaArchivo.Datos = archivo.ToString();

                                    protocoloServidor.EnviarTrama(tramaArchivo);

                                }
                                break;
                            case 40:
                                

                                string[] tokens = tramaRecibida.Datos.Split(delimitador);

                                string nombreArchivo = tokens[0];
                                string tamanioArchivo = tokens[1];

                                long tamanioArchivoLong = 0;

                                long.TryParse(tamanioArchivo, out tamanioArchivoLong);

                                int validarRecepcionArchivo = ValidarRecepcionArchivo(carpetaDeTrabajo + "\\" + nombreArchivo, tamanioArchivoLong);

                                if (protocoloServidor.RecibirArchivoRES(validarRecepcionArchivo) < 0)
                                {
                                    Console.WriteLine("SERVIDOR.Cargar: No se pudo validar la respuesta del servidor.\n");
                                    break;
                                }

                                

                                if (validarRecepcionArchivo == 0)
                                {
                                    Console.WriteLine("SERVIDOR: Recepcionando archivos\n");
                                    protocoloServidor.RecibirArchivoContenidoRES(tamanioArchivoLong, carpetaDeTrabajo + "\\" + nombreArchivo, Protocolo.Tipo.REQ, 41);

                                    using (Manejador manejador = new Manejador())
                                    {
                                        Evento evnt = new Evento();
                                        evnt.Comando = Evento.Comandos.CARGA;
                                        evnt.IPCliente = IPCliente;
                                        evnt.IdSesion = idSesion;
                                        evnt.Texto = "Se solicitó la carga del archivo " + nombreArchivo + " al servidor.";
                                        evnt.Timestamp = DateTime.Now;

                                        manejador.EnviarEventoWCF(evnt);
                                        EnviarIdSesion(idSesion, protocoloServidor);
                                }

                                }
                                else
                                {
                                    Console.WriteLine("SERVIDOR: No se pudo recepcionar el archivo.\n");
                                }
                                break;
                            case 60:
                                //Solicitud de descarga de archivo;
                                String archivoADescargar = tramaRecibida.Datos;
                                if (ExisteArchivo(archivoADescargar))
                                {
                                    long tamanioArchivoADescargar = TamanioArchivo(archivoADescargar);
                                    protocoloServidor.DescargarArchivoRES(62, tamanioArchivoADescargar);
                                    protocoloServidor.DescargarArchivoREQ(carpetaDeTrabajo + "\\" + archivoADescargar, tamanioArchivoADescargar);

                                    using (Manejador manejador = new Manejador())
                                    {
                                        Evento evnt = new Evento();
                                        evnt.Comando = Evento.Comandos.DESCARGA;
                                        evnt.IPCliente = IPCliente;
                                        evnt.IdSesion = idSesion;
                                        evnt.Texto = "Se solicitó la descarga del archivo " + archivoADescargar + ".";
                                        evnt.Timestamp = DateTime.Now;

                                        manejador.EnviarEventoWCF(evnt);
                                    }

                                    EnviarIdSesion(idSesion, protocoloServidor);
                                }
                                else
                                {
                                    protocoloServidor.DescargarArchivoRES(63, -1);
                                }
                                break;
                            case 70:
                                //Solicitud de sincronizacion
                                //Confirmo al cliente que me puedo sincronizar
                                

                                string[] datosServidorASincronizar = tramaRecibida.Datos.Split(delimitador);

                                String ipServidor = datosServidorASincronizar[0];

                                String puertoServidor = datosServidorASincronizar[1];

                                int puertoServidorASincronizar = 0;

                                int.TryParse(puertoServidor, out puertoServidorASincronizar);

                                

                                //Establezco una nueva conexion con el servidor

                                ConexionCliente conexionSincronizacion = new ConexionCliente();
                                conexionSincronizacion.AbrirConexion(ipServidor, puertoServidorASincronizar);

                                if (conexionSincronizacion.ConexionActiva())
                                {
                                    //Puedo sincronizarme

                                    //Notifico que puedo sincronizarme
                                    protocoloServidor.SincronizarRES(72);

                                    //Borro mis archivos

                                    Console.WriteLine("Servidor: Por solicitud de un cliente se borraran todos los archivos del servidor.");

                                    if (BorrarArchivosServidor())
                                    {
                                        Console.WriteLine("Servidor: Se borraron todos los archivos del servidor.");
                                        ProtocoloCliente clienteProtocolo = new ProtocoloCliente();
                                        clienteProtocolo.EstablecerConexion(conexionSincronizacion);

                                        clienteProtocolo.ListarArchivosREQ();

                                        ArrayList archivosServidorASincronizar=new ArrayList();
                                        archivosServidorASincronizar = clienteProtocolo.ListarArchivosRES();

                                        if(archivosServidorASincronizar.Count > 0){

                                            foreach (var archivo in archivosServidorASincronizar)
                                            {
                                                clienteProtocolo.DescargarArchivoREQ(archivo.ToString());

                                                long tamanio = clienteProtocolo.DescargarArchivoRES();

                                                if (tamanio != -1)
                                                {
                                                    Console.WriteLine("Recepcionando archivo...\n");
                                                    clienteProtocolo.DescargarArchivoRESContenido(carpetaDeTrabajo + "\\" + archivo.ToString(), tamanio);

                                                }
                                                else
                                                {
                                                    Console.WriteLine("ERROR: El archivo seleccionado no existe\n");
                                                }
                                            }
                                        }
                                        else
                                        {
                                            Console.WriteLine("Servidor: No existen archivos en el servidor remoto para sincronizar.");
                                        }
                                        
                                    }
                                    else
                                    {
                                        Console.WriteLine("Serivdor: Hubo un problema al borrar el servidor. Se aborta la sincronizacion.");
                                    }
                                    conexionSincronizacion.CerrarConexion();
                                }
                                else
                                {
                                    //No pude conectarme con el otro servidor.
                                    Console.WriteLine("Servidor: No puede establecer sincronizacion con el servidor " + ipServidor);
                                    protocoloServidor.SincronizarRES(73);
                                }
                                break;
                            case 90:
                                {
                                    String archivoAEliminar = tramaRecibida.Datos;
                                    if (ExisteArchivo(archivoAEliminar))
                                    {
                                        File.Delete(carpetaDeTrabajo + "\\" + archivoAEliminar);
                                        Console.WriteLine("EXITO: El archivo se elimino correctamente\n");
                                    }
                                    else
                                    {
                                        Console.WriteLine("ERROR: El archivo seleccionado no existe\n");
                                    }                                    
                                }
                                break;
                            case 0:
                                CerrarConexion(idSesion,IPCliente,protocoloServidor);
                                connected = false;
                                break;
                            default:
                                Console.WriteLine("SERVIDOR: El comando ingresado no es correcto.\n");
                                break;
                        }
                    }
                    else
                    {
                        CerrarConexion(idSesion, IPCliente, protocoloServidor);
                        connected = false;
                    }
                }
                catch (SocketException ex)
                {
                    Console.WriteLine("SERVIDOR: Ocurrió un error durante la conexión\n");
                    connected = false;
                }
            }
            clienteConexion.CerrarConexion();
            connected = false;
        }

        private static ArrayList listarArchivos() 
        {
            ArrayList retorno = new ArrayList();

            DirectoryInfo di = new DirectoryInfo(carpetaDeTrabajo);
            foreach (var item in di.GetFiles())
            {
                retorno.Add(item.Name);
            }
            return retorno;        
        }

        /* Verificamos que se pueda enviar el archivo.
         * 0 = Se puede recepcionar.
         * 1 = El archivo ya existe.
         * 2 = No queda espacio disponible.
         * */
        private static int ValidarRecepcionArchivo(String nombre, long tamanio)
        {
            int retorno = 0;

            //Verificamos que el archivo NO exista
            DirectoryInfo di = new DirectoryInfo(carpetaDeTrabajo);
            try
            {
                foreach (var item in di.GetFiles())
                {
                    if (string.Compare(item.Name.ToString(), nombre) == 0)
                    {
                        retorno = 1;
                        return retorno;
                    }
                }
            }
            catch (Exception ex)
            {
                Console.WriteLine("SERVIDOR: No se pudo acceder a la carpeta de trabajo.\n");
                return -1;
            }
            

            //Verificamos que quede espacio disponible

            long tamanioLong=0;

            long.TryParse(tamanio.ToString(), out tamanioLong);

            foreach (DriveInfo drive in DriveInfo.GetDrives())
            {
                if (drive.IsReady && (string.Compare(drive.Name.ToString(), "C:\\") == 0))
                {

                    if (drive.TotalFreeSpace <= tamanioLong)
                    {
                        retorno = 2;
                        return retorno;
                    }
                }
            }
            

            return retorno;

        }

        private static void CerrarConexion(string idSesion, string IPCliente, ProtocoloServidor protocoloServidor)
        {
            Console.WriteLine("SERVIDOR: El cliente " + idSesion + " cerró la conexión\n");

            using (Manejador manejador = new Manejador())
            {
                Evento evnt = new Evento();
                evnt.Comando = Evento.Comandos.DESCONEXION;
                evnt.IPCliente = IPCliente;
                evnt.IdSesion = idSesion;
                evnt.Texto = "Se desconecto el cliente " + IPCliente + ".";
                evnt.Timestamp = DateTime.Now;

                manejador.EnviarEventoWCF(evnt);
            }
            EnviarIdSesion(idSesion, protocoloServidor);
            Console.WriteLine("SERVIDOR: El cliente " + idSesion + " cerró la conexión\n");
            clientNumber--;
        }

        private static bool ExisteArchivo(String nombreArchivo)
        {

            if (System.IO.File.Exists(carpetaDeTrabajo + "\\" + nombreArchivo))
            {
                FileInfo f = new FileInfo(carpetaDeTrabajo + "\\" + nombreArchivo);
                return true;
            }
            return false;
        }

        private static long TamanioArchivo(String nombreArchivo)
        {
            long tamanio=0;
            if (System.IO.File.Exists(carpetaDeTrabajo + "\\" + nombreArchivo))
            {
                FileInfo f = new FileInfo(carpetaDeTrabajo + "\\" + nombreArchivo);
                tamanio = f.Length;
            }

            return tamanio;
        }

        private static bool BorrarArchivosServidor()
        {
            bool retorno = false;
            DirectoryInfo di = new DirectoryInfo(carpetaDeTrabajo);
            try
            {
                ArrayList archivos = new ArrayList();

                foreach (var item in di.GetFiles())
                {
                    item.Delete();
                }
                retorno = true;
            }
            catch (Exception ex)
            {
                Console.WriteLine("SERVIDOR: No se pudo acceder a la carpeta de trabajo.\n");
                retorno = false;
            }

            return retorno;
        }

        private static void EnviarIdSesion(string clientNumber,ProtocoloServidor protocoloServ)
        {
            Trama trama = new Trama();
            trama.Header = Protocolo.Tipo.RES;
            trama.Datos = clientNumber.ToString();
            trama.Comando = 81;
            trama.Largo = clientNumber.ToString().Length;
            protocoloServ.EnviarTrama(trama);
        }

        private static string ObtenerIdSesionGenerico()
        {
            string inputString = DateTime.Now.ToString();
            MD5 md5Hash = MD5.Create();
            Random rnd=new Random();
            int enteroRandomico=rnd.Next();

            String inputHash=inputString + enteroRandomico.ToString();

            String hash=GetMd5Hash(md5Hash, inputHash);

            return hash;
        }

        static string GetMd5Hash(MD5 md5Hash, string input)
        {

            // Convert the input string to a byte array and compute the hash.
            byte[] data = md5Hash.ComputeHash(Encoding.UTF8.GetBytes(input));

            // Create a new Stringbuilder to collect the bytes
            // and create a string.
            StringBuilder sBuilder = new StringBuilder();

            // Loop through each byte of the hashed data 
            // and format each one as a hexadecimal string.
            for (int i = 0; i < data.Length; i++)
            {
                sBuilder.Append(data[i].ToString("x2"));
            }

            // Return the hexadecimal string.
            return sBuilder.ToString();
        }

    }
}
